
/**
 * jQuery Slideshow - jQuery plugin
 * Version: 1.0 10/01/2010
 * Created by Daniel Kennedy
 * 
 * Copyrigth (c) 2010 Daniel Kennedy
 * 
 */

(function($){
	
	/**
	 * The different settings for the program. Users can pass these in as an array. The plugin has one required value and four optional values
	 *  - feed -> REQUIRED! :  An rss feed. Can specify the format (or let the plugin handle it)
	 *  - hold -> Default = 5000 : how long each slide remains in the center of the frame in miliseconds 
	 *  - transitionSpeed -> Default = 500 : speed the images fly in and out of the center in miliseconds. eg. if 500, it'll take 500 miliseconds for the image to reach the center 
	 *  - wait -> Default = 1000 : amount of time to wait between images flying in and flying out
	 *  - startIndex -> Default = 0 : The starting index of the list.
	 *  - showTitle -> Default = false : Takes the title from JSON feed and adds it to an <h1> tag overlayed onto of the image
	 *  - autoplay -> Default = true : If set to false, the initializer will set up the plugin but not start cycling the photos
	 */
	var settings = {
			'hold' : 5000,
			'transitionSpeed' : 500,
			'wait' : 1000,
			'feed' : null,
			'startIndex' : 0,
			'showTitle' : false,
			'autoplay' : true,
			'random' : true,
			'display' : 'standard',
			'embiggen' : 1
	};
	
	var imageIndex; // image index the slideshow is on (can be set manually for restarting the show)
	var modal; // the call to the focused image
	var state = 0; // 0 = stopped, > 0 if running
	var prevPhotos = new Array(); // array keeping track of the photos already shown (for random)
	var photos = new Array(); // array of photo objects
	var currentPhoto; // current photo at imageIndex
	var $this; // the calling object for the entire slideshow (where we append images and center them)
	var feedType;
	var displayPhotos; // number of photos on display for wall display type
	
	/**
	 * Methods include:
	 *  - init : initializes the plugin, reads the images from the feed and sets up the list of images
	 *  - rotate : rotates through each image in the list and brings them to and from the foreground (using flyOut(), flyIn()). Also creates the modal.
	 *  - flyOut : brings the modal to the front (flies in, fades in and grows)
	 *  - flyIn : flies the modal back to the small image (flies out, fades out and shrinks)
	 */
	var methods = {
			
		/**
		 * Sets up the plugin. Reads in the user options and resets the settings variable accordingly. Reads the JSON feed provided and creates an 
		 *  unordered list of thumbnails from using links from the feed. Calls the rotate() method periodically.
		 */
		init : function(options) {
		
			if (options) {
				feedType = options.feed.indexOf('google') > 0 ? 'picasa': 'flickr';
				if(feedType == 'flickr') {
					options.feed = options.feed.replace(/&format=rss_200/i, '');
					// check to see if the format is set for JSON
					if(options.feed.indexOf('&format=json') < 0) {
						options.feed = options.feed + '&format=json';
					}
					// specifies that we don't want JSONP
					if (options.feed.indexOf('&jsoncallback') < 0) {
						options.feed = options.feed + '&jsoncallback=?';
					}
				} else {
					options.feed = options.feed.replace(/alt=rss/i, 'alt=json');
					options.feed = options.feed + '&access=public';
				}
				if(feedType == 'picasa') {
					options.feed = options.feed + '&access=public';
				}
				$.extend(settings, options);
			}
			if (!$this) {
				$this = $(this);
			}
			return this.each(function() {
				
				// if the feed is supplied
				if(settings.feed) {
					$.getJSON(settings.feed, function(data){
						if(feedType == 'flickr'){
							$.each(data.items, function(i, item) {
								var width = item.description.substring(item.description.indexOf("width") + 7,item.description.indexOf("width") + 10); // get the width of the thumbnail
								var height = item.description.substring(item.description.indexOf("height") + 8,item.description.indexOf("height") + 11); // get the height of the thumbnail
								var photo = {
									src 	: item.media.m.replace('_m', '_z'), // large size photo
									thumb 	: item.media.m, // thumbnail for the photo
									title 	: item.title, // title of the photo
									width 	: width, // will get resized later
									height 	: height, // will get resized later
									tWidth 	: width,
									tHeight	: height,
									tSize	: width > height ? width : height
								};
								photos[i] = resize(photo, 800); // change the width and height to the real sizes (keeping aspect ratio)
							});
						} else {
							/*
							 * For parsing:
							 * 	- image content (src, height and width) in feed->entry[i]->media$group->media$content[0]
							 *  - thumb content (src, height and width) in feed->entry[i]->media$group->media$thumbnail[k] (0 <= k <= 2 with thumb sizes getting larger as k gets larger)
							 *  - title content in feed->entry[i]->media$group->media$title
							 */
							$.each(data.feed.entry, function(i, entry) {
								var content = entry.media$group.media$content[0];
								var thumb = entry.media$group.media$thumbnail[2];
								var title = entry.media$group.media$title;
								var photo = {
									src		: content.url, // full photo
									height	: content.height, // photo height
									width 	: content.width, // photo width
									thumb	: thumb.url, // thumbnail
									tHeight	: thumb.height, // thumbnail height
									tWidth	: thumb.width, // thumbnail width
									title	: title.$t // photo title
								}
								photos[i] = resize(photo, 800);
							});
						}
						
						/*
						 *  Set up the different displays depending on the display settings
						 */
						if (settings.display == 'wall') {
							
							// get the largest thumb height (for estimating how many rows we need)
							var maxThumbHeight = 0;
							$.each(photos, function(i, photo){
								if(maxThumbHeight < photo.tHeight)
									maxThumbHeight = photo.tHeight;
							});
							
							// create a list of thumbnails to be displayed at all times
							$("<ul id='thumbs'>").appendTo($this);
							
							// set up the first photo
							var listHeight = maxThumbHeight;
							var rowWidth = photos[0].tWidth;
							
							// loop through and add the remaining photos until the frame is full
							$.each(photos, function(i, photo){
								// check if we're at the end of the row
								if(rowWidth + photo.tWidth + 6 > $this.width()) {
									//check if we can fit another row
									if(listHeight + maxThumbHeight + 6 > $this.height()) {
										// if not, break
										return false;
									}
									
									// otherwise, start a new row and add to the list height
									rowWidth = 0;
									listHeight += maxThumbHeight;
								}
								
								// add to the row width
								rowWidth += photo.tWidth + 6;

								// add the photo to the list
								$("<img />").attr({
									src		: photo.thumb,
									title	: photo.title,
								})
								.appendTo("ul#thumbs")
								.wrap("<li class='photo'>");
								
								// keep track of the number of photos displayed
								displayPhotos = i;
							});
							
							setupWall();
							
						} else if (settings.display == 'stack') {

							setupStack();
							
						} else {
							
							setupStandard();
							
						}
					});

					// set the start index
					imageIndex = settings.startIndex;
					
					// if autoplay is set, then run
					if(settings.autoplay) {
						$.fn.slideshow('run');
					}
				}
				else {
					$.error('Feed not provided');
				}
			});
		},
		
		/**
		 * Begins cycling through the images. The user can also call the run method with a new set of options such as new times or 
		 * 	if specifying another feed (this will reinitialize the plugin)
		 */
		run : function() {
			if(!state) {
				state = setInterval("$.fn.slideshow('rotate')", settings.hold + settings.wait + 2*settings.transitionSpeed); // > 0 => shows it's running (passed when we want to stop the timer)
			}
		},
		
		/**
		 * Stops the cycle and clears the modal.
		 */
		stop : function() {
			if(state) {
				if(settings.display == 'wall') {
					if(modal) {
						modal.remove();
						$('div.overlay').remove();
					}
					$('li.photo').css('opacity','1');
				}
				clearInterval(state);
				state = 0;
			}
		},
		
		/**
		 * Refreshes the slideshow. Used mainly if the feed changes or the user wants to change settings.
		 */
		refresh : function() {
			$.fn.slideshow('stop'); // stops the slideshow
			if(settings.display == 'wall') { // if the slideshow is a wall
				imageIndex = 0; // reset the imageIndex
				modal = null; // destroy the modal
				//state = 0; // set the 
				prevPhotos.splice(0,prevPhotos.length); // empty the prevPhotos array
				photos.splice(0, photos.length); // empty the array of photos
				currentPhoto = null; // remove the currentPhoto
				$('ul#thumbs').remove(); // remove the list of photos
			} else if(settings.display == 'stack') {
				$photoStack.remove(); // remove the stack of photos
				$photoStack = null; // set the stack to null
			}
			// Recall the init() function
			$this.slideshow();
		},
		
		/**
		 * Rotates through the images, bringing each to the foreground, holding it there and sending it back to the background.
		 */
		rotate : function() {
			// if the modal exists, close it and remove the border around the corresponding li
			if (modal) {
				modal.remove();
				$('li.selected').removeClass('selected');
			}
			
			if (!currentPhoto) {
				currentPhoto = photos[imageIndex];
			}

			if (settings.display == 'wall') {
				
				// find next photo in the list
				$('li.photo').eq(imageIndex).addClass('selected');
				// create the larger image
				modal = createModal(settings.showTitle);

				var top = $this.height()/2 - currentPhoto.height/2;
				var left = $this.width()/2 - currentPhoto.width/2
				
				// animate the slideshow
				animateShow(modal,$('ul#thumbs'),top,left,imageIndex);
				
				prevPhotos.push(imageIndex);
				// increment the imageIndex to set the position of the next photo to be selected
				if(settings.random) { // generate a random number as the new index
					var rand = Math.floor(Math.random() * displayPhotos);
					var temp = rand % displayPhotos;

					// if the photo at [temp] has already been revealed, increment temp
					for (var i = 0; i < displayPhotos; i++) {
						if(temp == prevPhotos[i]) {
							temp = (temp + 1) % displayPhotos;
						}
					}

					imageIndex = temp;
					currentPhoto = photos[imageIndex];
					// clear the previous photos array once we come to the end of the list
					if(prevPhotos.length == displayPhotos) {
						prevPhotos.splice(0, displayPhotos);
					}
				} else { // if random isn't set, the photos cycle incrementally
					imageIndex = (imageIndex+1) % displayPhotos;
					currentPhoto = photos[imageIndex];
				}
			} else if (settings.display == 'stack') {
				
				$imgs = $("#photoStack img");
				$imgCount = $imgs.length;
				
				// get the top of the stack
				$thisImage = $('#photoStack img.current');

				// If the image is at the top of the stack
				if ($thisImage.hasClass('current')) {
					// Work out new z-index value
					$zi = $thisImage.css('zIndex') - $imgCount;

					// Trigger animation					
					$thisImage.addClass('animate');

					// Assign new z-index value then stop animation after complete 
					setTimeout(function() { $thisImage.css('zIndex', $zi); }, 500);
					setTimeout(function() { $thisImage.removeClass('animate'); }, 1000);

					// Set next image to current
					$thisImage.removeClass('current');
					if ($thisImage.index() == 0) {
						$imgs.last().addClass('current');
					} else {
						$imgs.eq($thisImage.index()-1).addClass('current');
					}
				} else { 
					// just in case it a current image isn't set
					$imgs.last().addClass('current');
				}
				
				
			} else {
				
				$imgs = $('img', $this);
				$imgCount = $imgs.length;
				
				// get the top of the stack
				$thisImage = $('img.current', $this);

				// If the image is at the top of the stack
				if ($thisImage.hasClass('current')) {
					
					$thisImage.fadeOut(settings.transitionSpeed);

					// Set next image to current
					$thisImage.removeClass('current');
					if ($thisImage.index() == 0) {
						$imgs.last().addClass('current');
					} else {
						$imgs.eq($thisImage.index()-1).addClass('current');
					}
				} else { 
					// just in case it a current image isn't set
					$imgs.last().addClass('current');
				}
				
				$('img.current', $this).fadeIn(settings.transitionSpeed);
			}
		},
		
		/**
		 * Animates the modal. Flies it to the foreground, growing it and making it more opaque simultaneously. Reverse of flyIn().
		 */
		flyOut : function(modal, thumbs, top, left) {
			return this.each(function(){
				thumbs.animate({opacity : 0.4}, settings.transitionSpeed);
				modal.animate({
					opacity: 1,
					width: currentPhoto.width,
					height: currentPhoto.height,
					top: top,
					left: left
				},settings.transitionSpeed);
			});
		},
		
		/**
		 * Animates the modal. Flies it back from the foreground, shrinking it and making it less opaque simultaneously. Reverse of flyOut().
		 */
		flyIn : function(modal, thumbs, index) {
			return this.each(function(){
				var position = $('li.photo>img').eq(index).position();
				var width = $('li.photo>img').eq(index).width();
				var height = $('li.photo>img').eq(index).height();

				thumbs.animate({opacity : 1}, settings.transitionSpeed);
				modal.animate({
					top: position.top + height/2,
					left: position.left + width/2,
					opacity: 'toggle',
					width: 0,
					height: 0,
				}, settings.transitionSpeed);
			});
		},
		
		destroy : function() {
			$.fn.slideshow('stop');
			$('ul#thumbs').remove();
		}
		
	};

	/**
	 * Main function
	 */
	$.fn.slideshow = function(method) {

		// Method calling logic
		if ( methods[method] ) {
			return methods[ method ].apply( this, Array.prototype.slice.call( arguments, 1 ));
		} else if ( typeof method === 'object' || ! method ) {
			return methods.init.apply( this, arguments );
		} else {
			$.error( 'Method ' +  method + ' does not exist on jQuery.tooltip' );
		}    
	};
	
	function animateShow(modal,thumbs,top,left,index) {
		setTimeout(function(){
			$this.slideshow('flyOut',modal,thumbs,top,left)
		}, settings.wait + settings.transitionSpeed);

		setTimeout(function(){
			$this.slideshow('flyIn',modal,thumbs,index)
		}, settings.hold + settings.transitionSpeed);
	}
	
	function resize(photo, size) {
		var newPhoto = photo;
		var ratio = newPhoto.width/newPhoto.height; // get the aspect ratio of the old image
		if(ratio > 1) { // the width is larger than height
				newPhoto.width = size;
				newPhoto.height = size/ratio;
		} else if(ratio < 1) { // same as above except inversed
				newPhoto.width = size*ratio;
				newPhoto.height = size;
		} else { // both are the same size, they are both the largest size
				newPhoto.width = size;
				newPhoto.height = size;
		}
		return newPhoto;
	}
	
	function createModal(title) {
		if(title) {
			$this.append("<div class='overlay'><img src='"+ currentPhoto.src + "' /><h1>" + currentPhoto.title + "</h1></div>");
		}
		else {
			$this.append("<div class='overlay'><img src='"+ currentPhoto.src + "' /></div>");
		}
		
		var thumb = $('li.photo').eq(imageIndex);
		var position = thumb.position();
		
		$('div.overlay').css({
			opacity: 0,
			width: 10,
			height: 10,
			position: 'absolute',
			top: position.top + (thumb.height()/2),
			left: position.left + (thumb.width()/2),
		});
		$('div.overlay>img').css({
			'border' : '1px solid #FFF',
			padding : '5px',
			width : currentPhoto.width,
			height : currentPhoto.height
		});
		$('div.overlay>h1').css({
			'font-family': 'sans-serif',
			'text-align': 'center',
			'font-size': '22px',
			color: '#FCFCFC',
			background: 'rgba(80, 80, 80, 0.75)',
			position: 'relative',
			top: '-55px',
			padding: '10px 0',
			margin: 'auto',
		});
		return $('div.overlay');
	}
	
	/**
	 * Set up the wall of images - styles for the ul and li elements
	 * @return
	 */
	function setupWall() {

		$('li.photo').css({
			margin : 3,
			display: 'inline'
		});
		$('ul#thumbs').css('list-style', 'none');
		$('ul#thumbs').css({
			'margin'		: 0,
			'padding-left'	: ($this.width() - $('ul#thumbs').width())/2,
			'padding-top'	: ($this.height() - $('ul#thumbs').height())/2
		});
	}
	
	function setupStack() {
		
		$.each(photos,function(i, photo){
			photo = resize(photo, 600);
		});

		$photoStack = $('<div id="photoStack">');

		$.each(photos, function(i, photo) {
			$('<img />')
			.attr({
				src: photo.src.replace('_m', '_z'),
				title: photo.title,
				height: photo.height,
				width: photo.width
			})
			.appendTo($photoStack);
		});	

		$photoStack.appendTo($this);
		
		$('#photoStack').css({
			'background'  : 'none',
			'top'         : '5%',
		    'left'        : '50%',
		    'width'       : '48em',
		    'height'      : '0em',
		    'margin-top'  : '0em',
		    'margin-left' : '-24em',
			'position'    : 'absolute'
		});
		
		$('#photoStack img').css({
			'position'           : 'absolute',
			'border'             : '10px solid #FFF',
			'box-shadow'         : '2px 2px 8px rgba(0,0,0,0.5)',
			'-moz-box-shadow'    : '2px 2px 8px rgba(0,0,0,0.5)',
			'-webkit-box-shadow' : '2px 2px 8px rgba(0,0,0,0.5)',
			'z-index'            : '9999',

			// setup transition speed and easing
			'moz-transition'     : 'all 0.2s ease',  // Firefox
			'-webkit-transition' : 'all 0.2s ease', // WebKit
			'-o-transition'      : 'all 0.2s ease', // Opera 
			'transition'         : 'all 0.2s ease' // Standard
		});
		
		$('#photoStack img').each(function() {
			
			var top = Math.floor(Math.random() * 100 + 1);
			var left = Math.floor(Math.random() * 100 + 1);
			var rotate = Math.floor(Math.random() * 8 + 1);
			
			$(this).css({
				'top'  : top + 'px',
				'left' : left + 'px'
			});
		});
		
		$imgs = $("#photoStack img");
		$imgs.last().addClass('current'); // Set the image at top of stack to current
	}
	
	/**
	 * Sets up the standard slideshow.
	 * Puts all images and their titles in the photos array into <img /> tags and 
	 * 	positions them in the center of the frame.
	 * @return
	 */
	function setupStandard() {
		
		var screenHeight = $this.height();
		var screenWidth = $this.width();

		// cycle through all the photos
		$.each(photos, function(i, photo) {
			// create the <img /> tag
			var $img = $('<img />').attr({
				src: photo.src.replace('_m', '_z'),
				title: photo.title
			});
			// append the image to the slide
			$img.appendTo($this);

			// get the current image's width and height
			var imgWidth = $img.width();
			var imgHeight = $img.height();

			/* If the fit to the frame setting is set and the 2x image can fit in the frame.
			*	Resize the image to fit in the frame
			*/ 
			if (settings.embiggen) {
			
				if ((imgWidth * 2 <= screenWidth) && imgHeight * 2 <= screenHeight) {
					imgWidth *= 2;
					imgHeight *= 2;
				}									
			}

			/*
			 * Set the css for the current image
			 */
			$img.css({
				width: imgWidth + 'px',
				height: imgHeight + 'px',
				position: 'absolute',
				top : $this.height()/2 - imgHeight/2,
				left : $this.width()/2 - imgWidth/2
			});
		});
		
		/*
		 * Set the css for all images in the frame
		 */
		$('img', $this).css({
			'position' : 'absolute'
		}).hide();
		
		$imgs = $('img', $this);
		$imgs.last().addClass('current').fadeIn(); // Set the image at top of stack to current
	}
	
})(jQuery);



